package org.eazegraph.lib.charts;

import ohos.agp.components.AttrSet;
import ohos.agp.components.Component;
import ohos.agp.render.Canvas;
import ohos.agp.render.Paint;
import ohos.agp.utils.Color;
import ohos.agp.utils.Rect;
import ohos.agp.utils.RectFloat;
import ohos.agp.utils.TextAlignment;
import ohos.app.Context;
import ohos.multimodalinput.event.TouchEvent;
import org.eazegraph.lib.models.BarModel;
import org.eazegraph.lib.models.BaseModel;
import org.eazegraph.lib.models.StackedBarModel;
import org.eazegraph.lib.utils.AttrUtils;
import org.eazegraph.lib.utils.Utils;

import java.util.ArrayList;
import java.util.List;


/**
 * A rather simple type of a bar chart, where all the bars have the same height and their inner bars
 * heights are dependent on each other.
 */
public class StackedBarChart extends BaseBarChart {
    /**
     * * The constant DEF_TEXT_SIZE
     */
    public static final float DEF_TEXT_SIZE = 12f;
    /**
     * * The constant DEF_SHOW_SEPARATORS
     */
    public static final boolean DEF_SHOW_SEPARATORS = false;
    /**
     * * The constant DEF_SEPARATOR_WIDTH
     */
    public static final float DEF_SEPARATOR_WIDTH = 2f;
    /**
     * * The constant LOG_TAG
     */
    private static final String LOG_TAG = BarChart.class.getSimpleName();
    /**
     * Constructor that is called when inflating a view from XML. This is called
     * when a view is being constructed from an XML file, supplying attributes
     * that were specified in the XML file. This version uses a default style of
     * 0, so the only attribute values applied are those in the Context's Theme
     * and the given AttributeSet.
     * <p/>
     * <p/>
     * The method onFinishInflate() will be called after all children have been
     * added.
     *
     * @param context The Context the view is running in, through which it can access the current theme, resources, etc.
     * @param attrs The attributes of the XML tag that is inflating the view.
     */
    private final String StackedBarChart_egBarTextSize = "egBarTextSize";
    /**
     * * The constant StackedBarChart_egShowSeparators
     */
    private final String StackedBarChart_egShowSeparators = "egShowSeparators";
    /**
     * * The constant StackedBarChart_egSeparatorWidth
     */
    private final String StackedBarChart_egSeparatorWidth = "egSeparatorWidth";
    /**
     * The constant M seperator paint
     */
    private Paint mSeperatorPaint;
    /**
     * The constant M text paint
     */
    private Paint mTextPaint;

    /**
     * The constant M data
     */
    private List<StackedBarModel> mData;

    /**
     * The constant M text size
     */
    private float mTextSize;
    /**
     * The constant M show separators
     */
    private boolean mShowSeparators;
    /**
     * The constant M separator width
     */
    private float mSeparatorWidth;

    /**
     * Simple constructor to use when creating a view from code.
     *
     * @param context The Context the view is running in, through which it can                access the current theme, resources, etc.
     */
    public StackedBarChart(Context context) {
        super(context);

        mTextSize = Utils.dpToPx(getContext(),DEF_TEXT_SIZE);
        mShowSeparators = DEF_SHOW_SEPARATORS;
        mSeparatorWidth = Utils.dpToPx(getContext(),DEF_SEPARATOR_WIDTH);

        initializeGraph();
    }

    /**
     * Stacked bar chart
     *
     * @param context context
     * @param attrs   attrs
     */
    public StackedBarChart(Context context, AttrSet attrs) {
        super(context, attrs);
        mTextSize = AttrUtils.getDimensionFromAttr(attrs, StackedBarChart_egBarTextSize, (int) Utils.dpToPx(getContext(),DEF_TEXT_SIZE));
        mShowSeparators = AttrUtils.getBooleanFromAttr(attrs, StackedBarChart_egShowSeparators, DEF_SHOW_SEPARATORS);
        mSeparatorWidth = AttrUtils.getDimensionFromAttr(attrs, StackedBarChart_egSeparatorWidth, (int) Utils.dpToPx(getContext(),DEF_SEPARATOR_WIDTH));
        initializeGraph();
    }

    /**
     * Returns the text size for the values which are shown in the bars.
     *
     * @return The text size in px
     */
    public float getTextSize() {
        return mTextSize;
    }

    /**
     * Sets the text size for the values which are shown in the bars.
     *
     * @param _textSize Size in sp
     */
    public void setTextSize(float _textSize) {
        mTextSize = Utils.dpToPx(getContext(),_textSize);
        onDataChanged();
    }

    /**
     * Returns the current state if the separator between the bars are shown or not
     *
     * @return True if the separators are shown
     */
    public boolean isShowSeparators() {
        return mShowSeparators;
    }

    /**
     * Sets the parameter if the separators between the bars should be shown or not
     *
     * @param _showSeparators True if the separators should be shown
     */
    public void setShowSeparators(boolean _showSeparators) {
        mShowSeparators = _showSeparators;
        invalidateGlobal();
    }

    /**
     * Returns the separator width.
     *
     * @return The separator width in px.
     */
    public float getSeparatorWidth() {
        return mSeparatorWidth;
    }

    /**
     * Sets the separator width.
     *
     * @param _separatorWidth The width in sp.
     */
    public void setSeparatorWidth(float _separatorWidth) {
        mSeparatorWidth = _separatorWidth;
        onDataChanged();
    }

    /**
     * Adds a new {@link StackedBarModel} to the BarChart.
     *
     * @param _Bar The StackedBarModel which will be added to the chart.
     */
    public void addBar(StackedBarModel _Bar) {
        if (_Bar == null) {
            return;
        }        
        mData.add(_Bar);
        onDataChanged();
    }

    /**
     * Adds a new list of {@link StackedBarModel} to the BarChart.
     *
     * @param _List The StackedBarModel list which will be added to the chart.
     */
    public void addBarList(List<StackedBarModel> _List) {
        if (_List == null) {
            return;
        }
        mData = _List;
        onDataChanged();
    }

    /**
     * Returns the data which is currently present in the chart.
     *
     * @return The currently used data.
     */
    @Override
    public List<StackedBarModel> getData() {
        return mData;
    }

    /**
     * Resets and clears the data object.
     */
    @Override
    public void clearChart() {
        mData.clear();
    }

    /**
     * On touch event boolean
     *
     * @param component  component
     * @param touchEvent touch event
     * @return the boolean
     */
    @Override
    public boolean onTouchEvent(Component component, TouchEvent touchEvent) {
        if (touchEvent.getAction() == TouchEvent.PRIMARY_POINT_DOWN) {
            simulateClick();
            return true;
        } else {
            return false;
        }
    }

    /**
     * This is the main entry point after the graph has been inflated. Used to initialize the graph
     * and its corresponding members.
     */
    @Override
    protected void initializeGraph() {
        super.initializeGraph();
        mData = new ArrayList<>();

        mSeperatorPaint = new Paint();
        mSeperatorPaint.setAntiAlias(true);
        mSeperatorPaint.setStyle(Paint.Style.FILLANDSTROKE_STYLE);
        mSeperatorPaint.setStrokeWidth(mSeparatorWidth);
        mSeperatorPaint.setColor(new Color(0xFFFFFFFF));

        mTextPaint = new Paint();
        mTextPaint.setAntiAlias(true);
        mTextPaint.setTextSize((int) mTextSize);
        mTextPaint.setTextAlign(TextAlignment.CENTER);
        mTextPaint.setColor(new Color(0xFFFFFFFF));
    }

    /**
     * Should be called after new data is inserted. Will be automatically called, when the view dimensions
     * has changed.
     */
    @Override
    protected void onDataChanged() {
        calculateBarPositions(mData.size());
        super.onDataChanged();
    }

    /**
     * Calculates the bar boundaries based on the bar width and bar margin.
     *
     * @param _Width  Calculated bar width
     * @param _Margin Calculated bar margin
     */
    protected void calculateBounds(float _Width, float _Margin) {
        int last = 0;

        for (StackedBarModel model : mData) {
            float lastY = 0;
            float cumulatedValues = 0;
            // used if seperators are enabled, to prevent information loss
            int usableGraphHeight = mGraphHeight - (int) (mSeparatorWidth * (model.getBars().size() - 1));

            for (BarModel barModel : model.getBars()) {
                cumulatedValues += barModel.getValue();
            }

            last += _Margin / 2;

            for (BarModel barModel : model.getBars()) {
                // calculate topX for the StackedBarModel part
                float newY = ((barModel.getValue() * usableGraphHeight) / cumulatedValues) + lastY;
                float height = newY - lastY;
                Rect textBounds = new Rect();
                String value = String.valueOf(barModel.getValue());
                textBounds = mTextPaint.getTextBounds(value);
                if (textBounds.getHeight() * 1.5f < height && textBounds.getWidth() * 1.1f < _Width) {
                    barModel.setShowValue(true);
                    barModel.setValueBounds(textBounds);
                }

                barModel.setBarBounds(new RectFloat(last, lastY, last + _Width, newY));
                lastY = newY;
            }
            model.setLegendBounds(new RectFloat(last, 0, last + _Width, mLegendHeight));

            last += _Width + (_Margin / 2);
        }

        Utils.calculateLegendInformation(getContext(), mData, 0, mContentRect.getWidth(), mLegendPaint);
    }

    /**
     * Callback method for drawing the bars in the child classes.
     *
     * @param _Canvas The canvas object of the graph view.
     */
    @Override
    protected void drawBars(Canvas _Canvas) {
        for (StackedBarModel model : mData) {
            float lastTop;
            float lastBottom = mGraphHeight;

            for (int index = 0; index < model.getBars().size(); index++) {
                BarModel barModel = model.getBars().get(index);

                RectFloat bounds = barModel.getBarBounds();
                mGraphPaint.setColor(new Color(barModel.getColor()));

                float height = (bounds.getHeight() * mRevealValue);
                lastTop = lastBottom - height;

                _Canvas.drawRect(new RectFloat(
                                bounds.left,
                                lastTop,
                                bounds.right,
                                lastBottom),
                        mGraphPaint
                );

                if (mShowValues && barModel.isShowValue()) {
                    _Canvas.drawText(mTextPaint,
                            String.valueOf(barModel.getValue()),
                            bounds.getCenter().getPointX(),
                            (lastTop + height / 2) + barModel.getValueBounds().getHeight() / 2
                    );
                }

                lastBottom = lastTop;

                if (mShowSeparators && index < model.getBars().size() - 1) {
                    lastBottom -= mSeparatorWidth;
                }
            }
        }
    }

    /**
     * Returns the list of data sets which hold the information about the legend boundaries and text.
     *
     * @return List of BaseModel data sets.
     */
    @Override
    protected List<? extends BaseModel> getLegendData() {
        return mData;
    }

    /**
     * Get bar bounds list
     *
     * @return the list
     */
    @Override
    protected List<RectFloat> getBarBounds() {
        ArrayList<RectFloat> bounds = new ArrayList<RectFloat>();
        for (StackedBarModel model : mData) {
            bounds.add(model.getBounds());
        }
        return bounds;
    }
}
